/*
 * GridPanel.cpp
 *
 * Copyright 2012 Pieter Agten
 *
 * This file is part of tltgen.
 *
 * tltgen is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * tltgen is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with tltgen.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * @file   GridPanel.cpp
 * @author Pieter Agten (pieter.agten@gmail.com)
 * @date   3 sep 2012
 */


#include <wx/wx.h>
#include <iostream>
#include <boost/lexical_cast.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/bind.hpp>

#include "Grid.hpp"
#include "CircuitDialog.hpp"
#include "ThermistorParamsPanel.hpp"

#include "GridPanel.hpp"


#define COL_T   0
#define COL_R   1 
#define COL_V   2
#define COL_ADC 3

using boost::shared_ptr;

namespace tltgen {

  BEGIN_EVENT_TABLE(GridPanel, wxPanel)
  EVT_GRID_CMD_CELL_CHANGE(ID_Grid, GridPanel::onGridCellChange)
  EVT_GRID_LABEL_RIGHT_CLICK(GridPanel::onGridLabelRightClick)
  EVT_MENU(ID_MenuRemoveRow, GridPanel::onRemoveRowClick)
  EVT_BUTTON(ID_CircuitSettingsButton, GridPanel::onCircuitSettingsButtonClicked)
  END_EVENT_TABLE()

  GridPanel::GridPanel(wxWindow* parent, wxWindowID id, const shared_ptr<ThermistorTable>& thermistorTable, const shared_ptr<LeastSquaresSolver>& leastSquaresSolver, const shared_ptr<TemperatureScale>& temperatureScale, const wxPoint& pos, const wxSize& size) :
  wxPanel(parent, id, pos, size),
    temperatureScale(temperatureScale),
    thermistorTable(thermistorTable),
    lastGridHeaderClicked(-1),
    circuitDialog(new CircuitDialog(this, wxID_ANY, wxT("Circuit Settings")))
  {
    if (! isValidTemperatureScale(temperatureScale)) {
      throw std::invalid_argument("The given temperature scale is invalid for this grid panel.");
    }
    if (! isValidThermistorTable(thermistorTable)) {
      throw std::invalid_argument("The given thermistor table is invalid for this grid panel.");
    }
    
    initializeComponents(thermistorTable, leastSquaresSolver, temperatureScale);

    this->grid->BeginBatch();
    int i = 0;
    for (std::list<ThermistorTableRow *>::const_iterator ci = thermistorTable->begin(); ci != thermistorTable->end(); ++ci) {
      addGridToTableMapping(i, *ci);
      (*ci)->connectObserver(boost::bind(&GridPanel::onThermistorTableRowChange, this, _1, _2, _3));
      updateGridRow(i, *ci);
      ++i;
    }
    ensureEmptyBottomGridRow();
    this->grid->EndBatch();
  }

  GridPanel::~GridPanel()
  { }

  void GridPanel::initializeComponents(const shared_ptr<ThermistorTable>& thermistorTable, const shared_ptr<LeastSquaresSolver>& leastSquaresSolver, const shared_ptr<TemperatureScale>& temperatureScale)
  {
    wxSizer* vbox = new wxBoxSizer(wxVERTICAL);

    this->grid = new Grid(this, ID_Grid, wxDefaultPosition, wxSize(100, 100));
    this->grid->CreateGrid(thermistorTable->getNbRows(), 4);
    this->grid->SetColLabelValue(COL_T,   _("T"));
    this->grid->SetColLabelValue(COL_R,   _("R"));
    this->grid->SetColLabelValue(COL_V,   _("V"));
    this->grid->SetColLabelValue(COL_ADC, _("Adc"));
    this->grid->DisableDragRowSize();
    this->grid->SetTabWrap(true);
    vbox->Add(this->grid, 1, wxEXPAND);

    wxPanel* bottomPanel = new wxPanel(this, wxID_ANY);
    wxSizer* hbox = new wxBoxSizer(wxHORIZONTAL);
    this->thermistorParamsPanel = new ThermistorParamsPanel(bottomPanel, wxID_ANY, thermistorTable, leastSquaresSolver, temperatureScale);
    hbox->Add(this->thermistorParamsPanel, 1, wxEXPAND | wxALL, 5);

    wxButton* btnCircuitSettings = new wxButton(bottomPanel, ID_CircuitSettingsButton, wxT("Circuit settings"));
    hbox->Add(btnCircuitSettings, 0, wxALL | wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT, 5);

    bottomPanel->SetSizer(hbox);
    vbox->Add(bottomPanel, 0, wxEXPAND);


    this->rowMenu = new wxMenu();
    this->rowMenu->Append(ID_MenuRemoveRow, "Remove row");


    this->SetSizer(vbox);
  }
 

  bool GridPanel::isValidThermistorTable(const shared_ptr<ThermistorTable>& thermistorTable)
 { 
    return thermistorTable.get() != NULL;
  }
  shared_ptr<ThermistorTable> GridPanel::getThermistorTable() const
  {
    return this->thermistorTable;
  }

  bool GridPanel::isValidLeastSquaresSolver(const shared_ptr<LeastSquaresSolver>& leastSquaresSolver)
  {
    return ThermistorParamsPanel::isValidLeastSquaresSolver(leastSquaresSolver);
  }
  shared_ptr<LeastSquaresSolver> GridPanel::getLeastSquaresSolver() const
  {
    return this->thermistorParamsPanel->getLeastSquaresSolver();
  }  

  bool GridPanel::isValidTemperatureScale(const shared_ptr<TemperatureScale>& temperatureScale)
  {
    return temperatureScale.get() != NULL;
  }
  void GridPanel::setTemperatureScale(const shared_ptr<TemperatureScale>& temperatureScale)
  {
    if (! isValidTemperatureScale(temperatureScale)) {
      throw std::invalid_argument("The given temperature scale is invalid for this grid panel.");
    }

    this->temperatureScale = temperatureScale;

    this->thermistorParamsPanel->setTemperatureScale(temperatureScale);
    for (int i = 0; i < this->grid->GetNumberRows(); ++i) {
      if (hasGridToTableMapping(i)) {
	ThermistorTableRow* tableRow = getGridToTableMapping(i);
	updateGridRowTemperature(i, tableRow);
      }
    }
  }
  shared_ptr<TemperatureScale> GridPanel::getTemperatureScale() const
  {
    return this->temperatureScale;
  }

  void GridPanel::addThermistorModel(const shared_ptr<ThermistorModel>& thermistorModel)
  {
    this->thermistorParamsPanel->addThermistorModel(thermistorModel);
  }

  void GridPanel::onThermistorTableRowChange(ThermistorTableRow::Event e, ThermistorTableRow *tableRow, ThermistorTableRow::Column col)
  {
    int gridRow = getTableToGridMapping(tableRow);
    switch (e) {
    case ThermistorTableRow::COLUMN_CHANGED:
      updateGridRowColumn(col, gridRow, tableRow);
      break;
    case ThermistorTableRow::REMOVE:
      removeGridRow(gridRow, tableRow);
      break;
    default:
      break;
    }
    ensureEmptyBottomGridRow();
  }

  bool GridPanel::isEmptyGridRow(int row) const
  {
    wxGridTableBase *table = this->grid->GetTable();
    bool result = true;
    for(int col = 0; col < table->GetNumberCols(); ++col) {
      result &= table->IsEmptyCell(row, col);
    }
    return result;
  }

  double GridPanel::getCellValueAsDouble(int row, int col) const 
  {
    double value = NAN;
    bool success = this->grid->GetCellValue(row, col).ToDouble(&value);
    if (success) {
      return value;
    } else {
      return NAN;
    }
  }

  int GridPanel::getCellValueAsPositiveInt(int row, int col) const 
  {
    long value = -1;
    bool success = this->grid->GetCellValue(row, col).ToLong(&value);
    if (success && value > 0 && value <= INT_MAX) {
      return (int)value;
    } else {
      return -1;
    }
  }

  void GridPanel::clearCellValue(int row, int col)
  {
    this->grid->SetCellValue(row, col, wxEmptyString);
  }

  bool GridPanel::isValidGridRowTemperature(int gridRow) const
  {
    Temperature t = getCellValueAsDouble(gridRow, COL_T);
    Temperature kelvin_t = getTemperatureScale()->toKelvin(t);
    return getThermistorTable()->isValidTemperature(kelvin_t);
  }
  Temperature GridPanel::getGridRowTemperature(int gridRow) const
  {
    if (! isValidGridRowTemperature(gridRow)) {
      throw std::runtime_error("Grid row " + boost::lexical_cast<std::string>(gridRow) + 
            " doesn't contain a valid temperature value.");
    }

    Temperature t = getCellValueAsDouble(gridRow, COL_T);
    return getTemperatureScale()->toKelvin(t);
  }

  bool GridPanel::isValidGridRowResistance(int gridRow) const
  {
    Resistance r = getCellValueAsDouble(gridRow, COL_R);
    return getThermistorTable()->isValidResistance(r);
  }
  Resistance GridPanel::getGridRowResistance(int gridRow) const
  {
    if (! isValidGridRowResistance(gridRow)) {
      throw std::runtime_error("Grid row " + boost::lexical_cast<std::string>(gridRow) + 
            " doesn't contain a valid resistance value.");
    }

    return getCellValueAsDouble(gridRow, COL_R);
  }

  bool GridPanel::isValidGridRowVoltage(int gridRow) const
  {
    Voltage v = getCellValueAsDouble(gridRow, COL_V);
    return getThermistorTable()->isValidVoltage(v);
  }
  Voltage GridPanel::getGridRowVoltage(int gridRow) const
  {
    if (! isValidGridRowVoltage(gridRow)) {
      throw std::runtime_error("Grid row " + boost::lexical_cast<std::string>(gridRow) + 
            " doesn't contain a valid voltage.");
    }

    return GridPanel::getCellValueAsDouble(gridRow, COL_V);
  }

  bool GridPanel::isValidGridRowAdcValue(int gridRow) const
  {
    int adc = getCellValueAsPositiveInt(gridRow, COL_ADC);
    return getThermistorTable()->isValidAdcValue(adc);
  }
  int GridPanel::getGridRowAdcValue(int gridRow) const
  {
    if (! isValidGridRowAdcValue(gridRow)) {
      throw std::runtime_error("Grid row " + boost::lexical_cast<std::string>(gridRow) + 
            " doesn't contain a valid ADC value.");
    }

    return getCellValueAsDouble(gridRow, COL_ADC);
  }

  wxString GridPanel::formatTemperature(Temperature t) const
  {
    return wxString::Format(wxT("%.1f"), t);
  }
  wxString GridPanel::formatResistance(Resistance r) const
  {
    return wxString::Format(wxT("%.0f"), r);
  }
  wxString GridPanel::formatVoltage(Voltage v) const
  {
    return wxString::Format(wxT("%.2f"), v);
  }
  wxString GridPanel::formatAdcValue(int adc) const
  {
    return wxString::Format(wxT("%d"), adc);
  }


  void GridPanel::updateGridRowColumn(ThermistorTableRow::Column col, int gridRow, ThermistorTableRow *tableRow)
  {
    switch (col) {
    case ThermistorTableRow::TEMPERATURE:
      updateGridRowTemperature(gridRow, tableRow);
      break;
    case ThermistorTableRow::RESISTANCE:
      updateGridRowResistance(gridRow, tableRow);
      break;
    case ThermistorTableRow::VOLTAGE:
      updateGridRowVoltage(gridRow, tableRow);
      break;
    case ThermistorTableRow::ADCVALUE:
      updateGridRowAdcValue(gridRow, tableRow);
      break;
    default:
      break;
    }
  }
  void GridPanel::updateGridRowTemperature(int gridRow, ThermistorTableRow *tableRow)
  {
    wxString cellValue;
    if (tableRow->hasTemperature()) {
      Temperature t = this->temperatureScale->fromKelvin(tableRow->getTemperature());
      cellValue = formatTemperature(t);
    }
    this->grid->SetCellValue(gridRow, COL_T, cellValue);
  }

  void GridPanel::updateGridRowResistance(int gridRow, ThermistorTableRow *tableRow)
  {
    wxString cellValue = tableRow->hasResistance() ? formatResistance(tableRow->getResistance()) : wxString(wxEmptyString); 
    this->grid->SetCellValue(gridRow, COL_R, cellValue);
  }
  void GridPanel::updateGridRowVoltage(int gridRow, ThermistorTableRow *tableRow)
  {
    wxString cellValue = tableRow->hasVoltage() ? formatVoltage(tableRow->getVoltage()) : wxString(wxEmptyString); 
    this->grid->SetCellValue(gridRow, COL_V, cellValue);
  }
  void GridPanel::updateGridRowAdcValue(int gridRow, ThermistorTableRow *tableRow)
  {
    wxString cellValue = tableRow->hasAdcValue() ? formatAdcValue(tableRow->getAdcValue()) : wxString(wxEmptyString); 
    this->grid->SetCellValue(gridRow, COL_ADC, cellValue);
  }

  void GridPanel::updateGridRow(int gridRow, ThermistorTableRow *tableRow)
  {
    updateGridRowTemperature(gridRow, tableRow);
    updateGridRowResistance(gridRow, tableRow);
    updateGridRowVoltage(gridRow, tableRow);
    updateGridRowAdcValue(gridRow, tableRow);
  }

  ThermistorTableRow::Column GridPanel::mapColumn(int col) const
  {
    switch (col) {
    case COL_T:
      return ThermistorTableRow::TEMPERATURE;
    case COL_R:
      return ThermistorTableRow::RESISTANCE;
    case COL_V:
      return ThermistorTableRow::VOLTAGE;
    case COL_ADC:
      return ThermistorTableRow::ADCVALUE;
    default:
      throw std::invalid_argument("Invalid column specified.");
    }
  }

  void GridPanel::onGridCellChange(wxGridEvent& event)
  {
    int gridRow = event.GetRow();
    ThermistorTableRow *tableRow;
    if (! hasGridToTableMapping(gridRow)) {
      // New row
      //std::cout << "New row: " << gridRow << std::endl;
      tableRow = getThermistorTable()->createRow();
      addGridToTableMapping(gridRow, tableRow);
      tableRow->connectObserver(boost::bind(&GridPanel::onThermistorTableRowChange, this, _1, _2, _3));
    } else {
      // Existing row
      //std::cout << "Existing row: " << gridRow << std::endl;
      tableRow = getGridToTableMapping(gridRow);
    }

    updateThermistorTableRow(tableRow, gridRow, mapColumn(event.GetCol()));

    checkRemoveGridRow(gridRow);
    ensureEmptyBottomGridRow();
  }

  void GridPanel::onGridLabelRightClick(wxGridEvent& event)
  {
    this->lastGridHeaderClicked = event.GetRow();
    if (event.GetRow() >= 0) {
      PopupMenu(this->rowMenu, event.GetPosition());
    }
  }

  void GridPanel::onRemoveRowClick(wxCommandEvent& event)
  {
    int gridRow = this->lastGridHeaderClicked;
    if (gridRow >= 0 && gridRow < this->grid->GetNumberRows() &&
	hasGridToTableMapping(gridRow)) {
      ThermistorTableRow* tableRow = getGridToTableMapping(gridRow);
      this->thermistorTable->removeRow(tableRow);
    }
  }

  void GridPanel::onCircuitSettingsButtonClicked(wxCommandEvent& event)
  {
    this->circuitDialog->setCircuitValues(this->thermistorTable->getCircuit());
    this->circuitDialog->setAdcValues(this->thermistorTable->getAnalogToDigitalConverter());
    if (this->circuitDialog->ShowModal() == wxID_OK) {
      this->thermistorTable->setCircuit(this->circuitDialog->createCircuit());
      this->thermistorTable->setAnalogToDigitalConverter(this->circuitDialog->createAnalogToDigitalConverter());
    }
  }

  void GridPanel::updateThermistorTableRow(ThermistorTableRow *tableRow, int gridRow, int gridCol)
  {
    switch(gridCol) {
    case COL_T:
      if (isValidGridRowTemperature(gridRow)) {
        tableRow->setTemperature(getGridRowTemperature(gridRow));
      } else {
        tableRow->clearTemperature();
      }
      break;
    case COL_R:
      if (isValidGridRowResistance(gridRow)) {
        tableRow->setResistance(getGridRowResistance(gridRow));
      } else {
        tableRow->clearResistanceVoltageAdc();
      }
      break;
    case COL_V:
      if (isValidGridRowVoltage(gridRow)) {
        tableRow->setVoltage(getGridRowVoltage(gridRow));
      } else {
        tableRow->clearResistanceVoltageAdc();
      }
      break;
    case COL_ADC:
      if (isValidGridRowAdcValue(gridRow)) {
        tableRow->setAdcValue(getGridRowAdcValue(gridRow));
      } else {
        tableRow->clearResistanceVoltageAdc();
      }
      break;
    }
  }

  void GridPanel::checkRemoveGridRow(int gridRow)
  {
    if (isEmptyGridRow(gridRow)) {
      getThermistorTable()->removeRow(getGridToTableMapping(gridRow));
    }
  }

  void GridPanel::removeGridRow(int gridRow, ThermistorTableRow* tableRow)
  {
    // Update mappings
    for (int i = gridRow; i < this->grid->GetNumberRows() - 2; ++i) {
      this->gridToTableMap[i] = this->gridToTableMap[i+1];
    }
    for (std::map<ThermistorTableRow*, int>::iterator it = this->tableToGridMap.begin(); it != this->tableToGridMap.end(); ++it) {
      if (it->second > gridRow) {
	it->second -= 1;
      }
    }

    //std::cout << "Erasing " << this->grid->GetNumberRows() - 2 << std::endl;
    this->gridToTableMap.erase(this->grid->GetNumberRows() - 2);
    this->tableToGridMap.erase(tableRow);

    this->grid->DeleteRows(gridRow);
  }

  void GridPanel::ensureEmptyBottomGridRow()
  {
    wxGridTableBase *table = this->grid->GetTable();
    int lastRow = table->GetNumberRows() - 1;
    if (table->GetNumberRows() == 0 || ! isEmptyGridRow(lastRow)) {
      table->AppendRows(1);
    } 
  }

 
  bool GridPanel::hasGridToTableMapping(int gridRow) const
  {
    return this->gridToTableMap.count(gridRow) > 0;
  }
  bool GridPanel::hasTableToGridMapping(ThermistorTableRow* tableRow) const
  {
    return this->tableToGridMap.count(tableRow) > 0;
  }

  ThermistorTableRow *GridPanel::getGridToTableMapping(int gridRow)
  {
    return this->gridToTableMap[gridRow];
  }
  int GridPanel::getTableToGridMapping(ThermistorTableRow* tableRow)
  {
    return this->tableToGridMap[tableRow];
  }
  void GridPanel::addGridToTableMapping(int gridRow, ThermistorTableRow *tableRow)
  {
    if (hasGridToTableMapping(gridRow)) {
      throw std::runtime_error("Error: grid row " + boost::lexical_cast<std::string>(gridRow)  + 
			       " is already mapped to a thermistor table row.");
    }
    if (hasTableToGridMapping(tableRow)) {
      throw std::runtime_error("Error: the table row is already mapped to a grid row.");
    }

    this->gridToTableMap[gridRow] = tableRow;
    this->tableToGridMap[tableRow] = gridRow;
  }
  /*void GridPanel::removeGridToTableMapping(int gridRow)
  {
    if (! hasGridToTableMapping(gridRow)) {
      throw std::runtime_error("Error: grid row " + boost::lexical_cast<std::string>(gridRow)  + 
			       " is not mapped to a thermistor table row.");
    }
    this->gridToTableMap.erase(gridRow);
    }*/

}


